﻿using Agora.Rtc;
using SharpDX;
using SharpDX.Direct3D9;
using SharpDX.Mathematics.Interop;
using System;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using System.Security.Policy;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Media3D;
namespace MyMeeting.Service
{
    public enum FrameFormat
    {
        YV12 = 0,
        NV12 = 1,
        YUY2 = 2,
        UYVY = 3,
        RGB15 = 10,
        RGB16 = 11,
        RGB24 = 12,
        RGB32 = 13,
        ARGB32 = 14
    }
    public class D3DImageSource : D3DImage, IDisposable
    {
        static Format D3DFormatYV12 = D3DX.MakeFourCC((byte)'Y', (byte)'V', (byte)'1', (byte)'2');
        static Format D3DFormatNV12 = D3DX.MakeFourCC((byte)'N', (byte)'V', (byte)'1', (byte)'2');
        static RawColorBGRA BlackColor = new RawColorBGRA(0, 0, 0, 0xFF);
        Int32Rect imageSourceRect;
        Direct3D direct3D;
        int adapterId;
        CreateFlags createFlag;
        Device device;
        Surface inputSurface;
        Texture texture;
        Surface textureSurface;
        DisplayMode displayMode;
        FrameFormat frameFormat;
        Format d3dFormat;
        int _width;
        int _height;
        bool isVistaOrBetter;
        bool isDisposed = false;
        private Size wndSize = new Size();
        private Size frameSize;
        private bool useSoftRender = false;

        public D3DImageSource(int videoWidth, int videoHeight, FrameFormat format, int adapterId = 0)
        {
           
            try
            {
                CheckHardWareSupport();
                isVistaOrBetter = IsVistaOrBetter;
                IsFrontBufferAvailableChanged += this.OnIsFrontBufferAvailableChanged;
                InitD3D(adapterId);
                 d3dFormat = ConvertToD3D(format);
                if (!this.CheckFormat(d3dFormat))
                {
                    // 显卡不支持该格式
                    throw new FormatException("显卡不支持该格式:" + format);
                }
                Capabilities deviceCap = this.direct3D.GetDeviceCaps(this.adapterId, DeviceType.Hardware);
                if (deviceCap.MaxTextureWidth < videoWidth || deviceCap.MaxTextureHeight < videoHeight)
                {
                    throw new Exception("显卡不支持分辨率:" + videoWidth + "*" + videoHeight);
                }
                this.frameFormat = format;
                _width = videoWidth;
                _height = videoHeight;
                AutoCreateD3DDevice(d3dFormat, 1920, 1080, videoWidth, videoHeight);
            }
            catch
            {
                Dispose();
                throw;
            }
        }
        /// <summary>
        /// 渲染
        /// </summary>
        /// <param name="data">数据，比如YU12放在data[0-2]、NV12放在data[0-1]、ARGB32放在data[0],具体参考ffmpeg frame.data</param>
        /// <param name="lineSize">linesize表示每行数据长度。具体参考ffmpeg frame.lineSize</param>
        public void Present(IntPtr[] data, int[] lineSize)
        {

            FillBuffer(data, lineSize);
            StretchSurface();
            InvalidateImage();
        }

        public void Present(VideoFrame frame)
        {
            FillBuffer(frame);
            StretchSurface(frame.width, frame.height);
            InvalidateImage();
        }
        [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
        protected static extern void CopyMemory(IntPtr Destination, IntPtr Source, int Length);

        /// <summary>
        /// 清除画面
        /// </summary>
        public void Clear()
        {
            this.device.ColorFill(this.textureSurface, BlackColor);
            InvalidateImage();
        }
        private void CheckHardWareSupport()//查询硬件支持
        {
            int level = System.Windows.Media.RenderCapability.Tier >> 16;
            useSoftRender = level == 0 ? true : false;
        }
        bool CheckFormat(FrameFormat format)
        {
            return this.CheckFormat(ConvertToD3D(format));
        }
        void InitD3D(int adapterId = 0)
        {
            this.adapterId = adapterId;
            this.direct3D = isVistaOrBetter ? new Direct3DEx() : new Direct3D();
            this.displayMode = this.direct3D.GetAdapterDisplayMode(this.adapterId);
            Capabilities deviceCap = this.direct3D.GetDeviceCaps(this.adapterId, DeviceType.Hardware);
            this.createFlag = CreateFlags.Multithreaded;
            if ((int)deviceCap.VertexProcessingCaps != 0)
            {
                this.createFlag |= CreateFlags.HardwareVertexProcessing;
            }
            else
            {
                this.createFlag |= CreateFlags.SoftwareVertexProcessing;
            }
        }
        void CreateResource(Format format, int width, int height)
        {
            var frame_width = 1920;
            var frame_height = 1080;
            System.Drawing.Rectangle dest_rect = CalcDestRectangle(new Size(width, height), wndSize);
            width = dest_rect.Width;
            height = dest_rect.Height;

          
            PresentParameters presentParameters = this.GetPresentParameters(frame_width, frame_height);
            this.device = isVistaOrBetter ?
              new DeviceEx((Direct3DEx)this.direct3D, this.adapterId, DeviceType.Hardware, IntPtr.Zero, this.createFlag, presentParameters) :
              new Device(this.direct3D, this.adapterId, DeviceType.Hardware, IntPtr.Zero, this.createFlag, presentParameters);

           
           
           
            this.texture = new Texture(this.device, width, height, 1, Usage.RenderTarget, this.displayMode.Format, Pool.Default);
            this.textureSurface = this.texture.GetSurfaceLevel(0);
            this.inputSurface = isVistaOrBetter ?
                Surface.CreateOffscreenPlainEx((DeviceEx)this.device, frame_width, frame_height, format, Pool.Default, Usage.None) :
                Surface.CreateOffscreenPlain(this.device, frame_width, frame_height, format, Pool.Default);
            this.device.ColorFill(this.inputSurface, BlackColor);
            this.SetImageSourceBackBuffer();
        }
        void ReleaseResource()
        {
            this.SafeRelease(this.inputSurface);
            this.SafeRelease(this.texture);
            this.SafeRelease(this.textureSurface);
            this.SafeRelease(this.device);
        }
        PresentParameters GetPresentParameters(int width, int height)
        {
            PresentParameters presentParams = new PresentParameters();
            presentParams.PresentFlags = PresentFlags.Video | PresentFlags.OverlayYCbCrBt709;
            presentParams.Windowed = true;
            presentParams.DeviceWindowHandle = IntPtr.Zero;
            presentParams.BackBufferWidth = width == 0 ? 1 : width;
            presentParams.BackBufferHeight = height == 0 ? 1 : height;
            presentParams.SwapEffect = SwapEffect.Discard;
            presentParams.PresentationInterval = PresentInterval.Immediate;
            presentParams.BackBufferFormat = this.displayMode.Format;
            presentParams.BackBufferCount = 1;
            presentParams.EnableAutoDepthStencil = false;
            return presentParams;
        }
        void FillBuffer(IntPtr[] data, int[] lineSize)
        {
            DataRectangle rect = this.inputSurface.LockRectangle(LockFlags.None);
            IntPtr surfaceBufferPtr = rect.DataPointer;
            switch (this.frameFormat)
            {
                case FrameFormat.YV12:
                    {
                        if (lineSize[0] == rect.Pitch)
                        {
                            int ySize = lineSize[0] * (int)_height;
                            int uSize = lineSize[1] * (int)_height / 2;
                            int vSize = lineSize[2] * (int)_height / 2;
                            Memcpy(surfaceBufferPtr, data[0], ySize);
                            surfaceBufferPtr += ySize;
                            Memcpy(surfaceBufferPtr, data[1], uSize);
                            surfaceBufferPtr += uSize;
                            Memcpy(surfaceBufferPtr, data[2], vSize);
                        }
                        else
                        {
                            int dataSize = rect.Pitch < lineSize[0] ? rect.Pitch : lineSize[0];
                            for (int i = 0; i < _height; i++)
                            {
                                Memcpy(surfaceBufferPtr, data[0] + i * lineSize[0], dataSize);
                                surfaceBufferPtr += rect.Pitch;
                            }
                            dataSize = rect.Pitch < lineSize[1] ? rect.Pitch : lineSize[1];
                            for (int i = 0; i < _height / 2; i++)
                            {
                                Memcpy(surfaceBufferPtr, data[1] + i * lineSize[1], dataSize);
                                surfaceBufferPtr += rect.Pitch / 2;
                            }

                            dataSize = rect.Pitch < lineSize[2] ? rect.Pitch : lineSize[2];
                            for (int i = 0; i < _height / 2; i++)
                            {
                                Memcpy(surfaceBufferPtr, data[2] + i * lineSize[2], dataSize);
                                surfaceBufferPtr += rect.Pitch / 2;
                            }
                        }
                    }
                    break;
                case FrameFormat.NV12:
                    {

                        if (lineSize[0] == rect.Pitch)
                        {
                            int ySize = lineSize[0] * (int)_height;
                            int uvSize = lineSize[1] * (int)_height / 2;
                            Memcpy(surfaceBufferPtr, data[0], ySize);
                            surfaceBufferPtr += ySize;
                            Memcpy(surfaceBufferPtr, data[1], uvSize);
                        }
                        else
                        {
                            int dataSize = rect.Pitch < lineSize[0] ? rect.Pitch : lineSize[0];
                            for (int i = 0; i < _height; i++)
                            {
                                Memcpy(surfaceBufferPtr, data[0] + i * lineSize[0], dataSize);
                                surfaceBufferPtr += rect.Pitch;
                            }

                            dataSize = rect.Pitch < lineSize[1] ? rect.Pitch : lineSize[1];
                            for (int i = 0; i < _height / 2; i++)
                            {
                                Memcpy(surfaceBufferPtr, data[1] + i * lineSize[1], dataSize);
                                surfaceBufferPtr += rect.Pitch;
                            }
                        }
                    }
                    break;
                case FrameFormat.YUY2:
                case FrameFormat.UYVY:
                case FrameFormat.RGB15:
                case FrameFormat.RGB16:
                case FrameFormat.RGB24:
                case FrameFormat.RGB32:
                case FrameFormat.ARGB32:
                default:
                    if (lineSize[0] == rect.Pitch)
                    {
                        Memcpy(surfaceBufferPtr, data[0], lineSize[0] * (int)_height);
                    }
                    else
                    {
                        int dataSize = rect.Pitch < lineSize[0] ? rect.Pitch : lineSize[0];
                        for (int i = 0; i < _height; i++)
                        {
                            Memcpy(surfaceBufferPtr, data[0] + i * lineSize[0], dataSize);
                            surfaceBufferPtr += rect.Pitch;
                        }
                    }
                    break;
            }
            this.inputSurface.UnlockRectangle();
        }

        void FillBuffer(VideoFrame videoFrame)
        {
            AutoCreateD3DDevice(d3dFormat, videoFrame.width, videoFrame.height, (int)wndSize.Width, (int)wndSize.Height);
            SharpDX.DataRectangle locked_data = new DataRectangle();
            //locked_data = this.inputSurface.LockRectangle(LockFlags.None);
            const int MAX_TRY = 3;
            int times = 0;
            do
            {
                bool need_retry = false;
                try
                {
                    locked_data = this.inputSurface.LockRectangle(SharpDX.Direct3D9.LockFlags.DoNotWait);
                }
                catch (SharpDX.SharpDXException e)
                {
                    // D3DERR_WASSTILLDRAWING
                    if (e.Message.Contains("WASSTILLDRAWING"))
                        need_retry = true;
                    else
                    {
                        Console.WriteLine("d3d lock surface failed {0}", e.Message);
                        return;
                    }
                }

                if (need_retry != true)
                    break;

                times++;

                // should sleep in microseconds

            } while (times < MAX_TRY);


            if (locked_data.DataPointer == IntPtr.Zero)
            {
                Console.WriteLine("d3d failed to lock offscreen surface");
                return;
            }

            try
            {


                for (int i = 0; i < videoFrame.height; i++)
                {
                    CopyMemory(locked_data.DataPointer + locked_data.Pitch * i, videoFrame.yBufferPtr + i * videoFrame.yStride, Math.Min(videoFrame.yStride, locked_data.Pitch));
                }
            }
            catch (SharpDX.SharpDXException e)
            {

                throw;
            }
            catch (Exception e)
            {
                throw;
            }

            this.inputSurface.UnlockRectangle();
        }

        protected virtual System.Drawing.Rectangle CalcDestRectangle(Size frameSize, Size controlSize)
        {
            // 计算目标矩形，保持宽高比
            float frameAspectRatio = (float)(frameSize.Width / frameSize.Height);
            float controlAspectRatio = (float)(controlSize.Width / controlSize.Height);

            int destWidth, destHeight;

            if (frameAspectRatio > controlAspectRatio)
            {
                // 以宽度为准，高度自适应
                destWidth = (int)controlSize.Width;
                destHeight = (int)(controlSize.Width / frameAspectRatio);
            }
            else
            {
                // 以高度为准，宽度自适应
                destHeight = (int)controlSize.Height;
                destWidth = (int)(controlSize.Height * frameAspectRatio);
            }

            // 居中显示
            int x = (int)((controlSize.Width - destWidth) / 2);
            int y = (int)((controlSize.Height - destHeight) / 2);

            return new System.Drawing.Rectangle(x, y, destWidth, destHeight);
        }
        protected virtual int OddRectangleValue(int value)
        {
            if (value % 2 == 0)
                return value;

            if (value > 1)
                return value - 1;
            else
                return value + 1;
        }


        private bool AutoCreateD3DDevice(Format format, int frame_width, int frame_height, int wnd_width, int wnd_height)
        {
            if (wndSize.Width == wnd_width && wndSize.Height == wnd_height &&
                frameSize.Width == frame_width && frameSize.Height == frame_height)
                return true;
            if (this.inputSurface != null)
                this.inputSurface.Dispose();
            if (this.device != null)
                this.device.Dispose();

            int adapter_index = -1;
            SharpDX.Direct3D9.Format adapter_format = SharpDX.Direct3D9.Format.X8R8G8B8;
            SharpDX.Direct3D9.DeviceType device_type = SharpDX.Direct3D9.DeviceType.Hardware;


            PresentParameters presentParameters = this.GetPresentParameters(frame_width, frame_height);
            this.device = isVistaOrBetter ?
              new DeviceEx((Direct3DEx)this.direct3D, this.adapterId, DeviceType.Hardware, IntPtr.Zero, this.createFlag, presentParameters) :
              new Device(this.direct3D, this.adapterId, DeviceType.Hardware, IntPtr.Zero, this.createFlag, presentParameters);

            this.texture = new Texture(this.device, wnd_width, wnd_height, 1, Usage.RenderTarget, this.displayMode.Format, Pool.Default);
            this.textureSurface = this.texture.GetSurfaceLevel(0);
            this.inputSurface = isVistaOrBetter ?
                Surface.CreateOffscreenPlainEx((DeviceEx)this.device, frame_width, frame_height, format, Pool.Default, Usage.None) :
                Surface.CreateOffscreenPlain(this.device, frame_width, frame_height, format, Pool.Default);
            this.device.ColorFill(this.inputSurface, BlackColor);
            this.SetImageSourceBackBuffer();

            this.device.SetRenderState(SharpDX.Direct3D9.RenderState.CullMode, 1 /* D3DCULL_NONE = 1 */);
            this.device.SetRenderState(SharpDX.Direct3D9.RenderState.Lighting, false);
            frameSize.Width = frame_width;
            frameSize.Height = frame_height;

            wndSize.Width = wnd_width;
            wndSize.Height = wnd_height;
            frameSize.Width = frame_width;
            frameSize.Height = frame_height;

            wndSize.Width = wnd_width;
            wndSize.Height = wnd_height;

            return true;
        }


        void StretchSurface(int fw, int fh)
        {

            System.Drawing.Rectangle dest_rect = CalcDestRectangle(new System.Windows.Size(fw, fh), wndSize);
           
            // 确保目标矩形不超出控件范围
            dest_rect.X = Math.Max(0, dest_rect.X);
            dest_rect.Y = Math.Max(0, dest_rect.Y);
            dest_rect.Width = Math.Min((int)wndSize.Width, dest_rect.Width);
            dest_rect.Height = Math.Min((int)wndSize.Height, dest_rect.Height);
            SharpDX.Mathematics.Interop.RawRectangle src_native_rect = new SharpDX.Mathematics.Interop.RawRectangle(0, 0, fw, fh);
            SharpDX.Mathematics.Interop.RawRectangle dst_native_rect = new SharpDX.Mathematics.Interop.RawRectangle(OddRectangleValue(dest_rect.X), OddRectangleValue(dest_rect.Y), OddRectangleValue(dest_rect.Right), OddRectangleValue(dest_rect.Bottom));
            this.device.StretchRectangle(this.inputSurface, src_native_rect, this.textureSurface, dst_native_rect, SharpDX.Direct3D9.TextureFilter.Linear);
        }
        void StretchSurface()
        {
            this.device.StretchRectangle(this.inputSurface, this.textureSurface, TextureFilter.Linear);
        }
        /// <summary>
        /// 检查d3d设备是否正常
        /// </summary>
        /// <returns></returns>
        bool CheckDevice()
        {
            if (isVistaOrBetter)
            {
                DeviceState state = ((DeviceEx)this.device).CheckDeviceState(IntPtr.Zero);
                return state == DeviceState.Ok;
            }
            else
            {
                return false; // xp无法支持ex
            }
        }
        void OnIsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (!useSoftRender)//硬件加速，IsFrontBufferAvailable为false返回
            {
                if (!this.IsFrontBufferAvailable)
                {
                    return;
                }
            }

            if (this.textureSurface != null)
            {
                Lock();
                SetBackBuffer(D3DResourceType.IDirect3DSurface9, this.textureSurface.NativePointer, useSoftRender);
                Unlock();
            }
        }
        void SetImageSourceBackBuffer()
        {
            if (!Dispatcher.CheckAccess())
            {
                Dispatcher.Invoke((Action)(() => this.SetImageSourceBackBuffer()));
                return;
            }
            if (!useSoftRender)//硬件加速，IsFrontBufferAvailable为false返回
            {
                if (!this.IsFrontBufferAvailable)
                {
                    return;
                }
            }

           
            Lock();
            SetBackBuffer(D3DResourceType.IDirect3DSurface9, this.textureSurface.NativePointer, useSoftRender);
            Unlock();
            this.imageSourceRect = new Int32Rect(0, 0, PixelWidth, PixelHeight);
        }
        void InvalidateImage()
        {
            this.Dispatcher.Invoke(new Action(() =>
            {
                if (!IsFrontBufferAvailable)
                {
                    return;
                }
                Lock();
                AddDirtyRect(this.imageSourceRect);
                Unlock();
            }));

        }
        bool CheckFormat(Format d3dFormat)
        {
            if (!this.direct3D.CheckDeviceFormat(this.adapterId, DeviceType.Hardware, this.displayMode.Format, Usage.None, ResourceType.Surface, d3dFormat))
            {
                return false;
            }
            return this.direct3D.CheckDeviceFormatConversion(this.adapterId, DeviceType.Hardware, d3dFormat, this.displayMode.Format);
        }
        static Format ConvertToD3D(FrameFormat format)
        {
            switch (format)
            {
                case FrameFormat.YV12:
                    return D3DFormatYV12;
                case FrameFormat.NV12:
                    return D3DFormatNV12;
                case FrameFormat.YUY2:
                    return Format.Yuy2;
                case FrameFormat.UYVY:
                    return Format.Uyvy;
                case FrameFormat.RGB15:
                    return Format.X1R5G5B5;
                case FrameFormat.RGB16:
                    return Format.R5G6B5;
                case FrameFormat.RGB32:
                    return Format.X8R8G8B8;
                case FrameFormat.ARGB32:
                    return Format.A8R8G8B8;
                case FrameFormat.RGB24:
                    return Format.R8G8B8;
                default:
                    throw new ArgumentException("Unknown pixel format", "format");
            }
        }
        static int GetArgb(byte a, byte r, byte g, byte b)
        {
            return a << 24 + r << 16 + g << 8 + b;
        }

        static bool IsVistaOrBetter
        {
            get
            {
                return Environment.OSVersion.Version.Major >= 6;
            }
        }
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        void Dispose(bool disposing)
        {
            if (!this.isDisposed)
            {
                this.isDisposed = true;
                if (disposing)
                {
                    this.ReleaseResource();
                    this.SafeRelease(this.direct3D);
                }
            }
        }
        void SafeRelease(IDisposable item)
        {
            try
            {
                if (item != null)
                {
                    item.Dispose();
                }
            }
            catch
            {
            }
        }
        public void UpdateSize(Size sz)
        {
            wndSize = sz;
        }
        [DllImport("ntdll.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl)]
        static extern IntPtr Memcpy(IntPtr dest, IntPtr source, int length);

       
    }
}
